package com.site.common.filter.handler;

import com.site.common.EntryRequest;
import com.site.common.annotation.EncryRSA;
import com.site.common.filter.AbstractBaseFilter;
import com.site.common.filter.wrapper.EntryRequestWrapper;
import com.site.controller.EntryRSATestController;
import com.site.util.AESUtil;
import com.site.util.JacksonUtil;
import com.site.util.RSAUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.method.HandlerMethod;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Objects;

/**
 * 加密解密过滤器
 */
@Component
public class EntryRequestFilter extends AbstractBaseFilter<EntryRequestWrapper> {

    @Override
    public EntryRequestWrapper execute(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, ApplicationContext context){
        String method = request.getMethod();
        EntryRequestWrapper entryRequestWrapper = null;
        try {
            entryRequestWrapper = new EntryRequestWrapper(request);
        } catch (IOException e) {
            logger.error("自定义 EncryRequestWrapper 对象出错, {}",e);
        }
        // 判断方法是否添加了自定义加密注解 @EncryRSA
        if (entryRequestWrapper != null && handlerMethod.hasMethodAnnotation(EncryRSA.class)){
            EncryRSA encryRSA = handlerMethod.getMethodAnnotation(EncryRSA.class);
            Assert.isTrue(!encryRSA.method().equals(method),"暂不支持" + method + "请求方式");
            String bodyString = entryRequestWrapper.getBodyString();
            Assert.isTrue(!ObjectUtils.isEmpty(bodyString),"请求体不能为空");
            logger.info("请求参数：" + bodyString);
            EntryRequest entryRequest = null;
            try {
                entryRequest = JacksonUtil.jsonToBean(bodyString, EntryRequest.class);
            } catch (Exception e) {
                e.printStackTrace();
                logger.info("请求参数解析json错误：" + bodyString);
            }
            Assert.isTrue(!Objects.isNull(entryRequest),"参数不能为空");
            // 1.参数合法性校验（非空判断等）
            parameterValidate(entryRequest);
            // 2.判断请求合法性。1.不允许重复请求（通过请求唯一id判断）2.不允许请求时间与当前时间差距过大（正负10分钟）
            String body = decrypt(entryRequest);
            Assert.isTrue(!ObjectUtils.isEmpty(body),"请求参数body不能为空");
            // 3.报文传递至controller层
            entryRequestWrapper.setBodyString(body.getBytes(StandardCharsets.UTF_8));
        }
        return entryRequestWrapper;
    }

    /**
     * 解密数据
     * @param entryRequest
     * @return
     */
    private String decrypt(EntryRequest entryRequest){
        // 2.判断请求合法性。1.不允许重复请求（通过请求唯一id判断）2.不允许请求时间与当前时间差距过大（正负10分钟）
        long currentTime = System.currentTimeMillis();
        long subTime = currentTime - entryRequest.getTimestamp();
        long tenMinuteMs = 10 * 60 * 1000;
        Assert.isTrue(!(subTime < -tenMinuteMs || subTime > tenMinuteMs),"请求异常，请求时间异常");
        // 3.重复请求，可以借助 redis ，这里先不验证
        // ...
        // 4.参数解密，签名校验，参数过滤和传递
        Map<String, Object> paramsMap = RSAUtil.bean2Map(entryRequest);
        paramsMap.remove("sign");
        // 参数排序
        Map<String, Object> sortedMap = RSAUtil.sort(paramsMap);
        // 拼接参数：key1Value1key2Value2
        String urlParams = RSAUtil.groupStringParam(sortedMap);
        // 根据appkey获取rsa密钥，这里我写死进行测试，实际开发中，从缓存读取
        /*String bodyJsonString = RedisUtil.get(encryRequest.getAppId());
        Assert.isTrue(!ObjectUtils.isEmpty(bodyJsonString),"bodyJsonString");
        JSONObject jsonObject = JSONObject.parseObject(bodyJsonString);
        // rsa公钥
        String publicKey = jsonObject.getString("publicKey");
        // rsa私钥
        String privateKey = jsonObject.getString("privateKey");*/
        String publicKey = EntryRSATestController.publicKey;// 实际开发中，从缓存读取
        String privateKey = EntryRSATestController.privateKey;// 实际开发中，从缓存读取
        //签名验证（使用参数，公钥，签名进行验证签名）
        boolean verify = RSAUtil.verify(urlParams.getBytes(), publicKey, entryRequest.getSign());
        Assert.isTrue(verify,"签名验证失败");
        // 5. 私钥解密，获取aseKey（公钥加密生成 aseKey）
        String asekey = RSAUtil.decryptByPrivateKey(entryRequest.getAseKey().getBytes(), privateKey);
        // 解密请求报文
        String body = "";
        try {
            // AES 解密业务参数
            body = AESUtil.decrypt(entryRequest.getBody(), asekey, entryRequest.getAppId().substring(16));
        } catch (Exception e) {
            logger.error("请求参数解密异常：",e);
            throw new RuntimeException("请求参数解密异常");
        }
        return body;
    }

    /**
     * 验证加密参数
     * @param entryRequest
     */
    private void parameterValidate(EntryRequest entryRequest) {
        Assert.isTrue(!ObjectUtils.isEmpty(entryRequest.getAppId()),"参数异常，appId不能为空");
        Assert.isTrue(!ObjectUtils.isEmpty(entryRequest.getAseKey()),"参数异常，aseKey不能为空");
        Assert.isTrue(!ObjectUtils.isEmpty(entryRequest.getRequestId()),"参数异常，requestId不能为空");
        Assert.isTrue(!ObjectUtils.isEmpty(entryRequest.getSign()),"参数异常，sign不能为空");
        Assert.isTrue(!Objects.isNull(entryRequest.getTimestamp()) || !ObjectUtils.isEmpty(entryRequest.getTimestamp() == 0L),"参数异常，timestamp不能为空");
    }
}
